/*
 * CCVisu is a tool for visual graph clustering
 * and general force-directed graph layout.
 * This file is part of CCVisu.
 *
 * Copyright (C) 2005-2011  Dirk Beyer
 *
 * CCVisu is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * CCVisu is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with CCVisu; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Please find the GNU Lesser General Public License in file
 * license_lgpl.txt or http://www.gnu.org/licenses/lgpl.txt
 *
 * Dirk Beyer    (firstname.lastname@uni-passau.de)
 * University of Passau, Bavaria, Germany
 */
package org.sosy_lab.ccvisu.graph;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.sosy_lab.ccvisu.graph.GraphVertex.Shape;
import org.sosy_lab.util.Colors;

/**
 * A class with a list of nodes that compute some informations on them
 */
public class Group implements Iterable<GraphVertex> {

  public enum GroupKind {
    USERDEFINED, CLUSTER
  };

  /** used as mode for the method addPattern */
  public static final int   EQUALS        = 0;
  /** used as mode for the method addPattern */
  public static final int   CONTAINS      = 1;
  /** used as mode for the method addPattern */
  public static final int   STARTS        = 2;
  /** used as mode for the method addPattern */
  public static final int   ENDS          = 3;

  /** name of the group */
  private String            name;
  /** list of nodes in the GraphData */
  private List<GraphVertex> nodes         = new ArrayList<GraphVertex>();
  /** color of the group */
  private Color             color         = Colors.RED.get();
  /** shape of the group */
  private Shape             shape         = Shape.DISC;
  /** kind of the group */
  private GroupKind         kind          = GroupKind.USERDEFINED;
  /** a list of patterns to match strings */
  private List<String>      patterns      = new ArrayList<String>();
  /** true if needs to recompute radius, center, ... */
  private boolean           changed       = true;
  /** pointer to the graph data */
  private GraphData         graphData     = null;
  /** the radius of the group */
  private RadiusOfGroup     radiusOfGroup = null;

  /** used to know if the group should be drawn */
  private boolean           visible       = true;
  /** used to know if the circle and cross should be drawn */
  private boolean           info          = false;

  /**
   * Constructor
   * @param name the group's name
   */
  public Group(String name, GraphData graphData) {
    this.name = name;
    this.graphData = graphData;
  }

  /**
   * Constructor
   * @param name the group's name
   * @param color the group's color
   * @param shape the group's shape
   * @param graphData the group's graph data
   */
  public Group(String name, Color color, Shape shape, GraphData graphData) {

    this.name = name;
    this.color = color;
    this.shape = shape;
    this.graphData = graphData;
  }

  /**
   * add the given node to the group
   * @param vertex
   */
  public void addNode(GraphVertex vertex) {
    vertex.setColor(color);
    vertex.setShape(shape);
    addNode_WO_COLOR(vertex);
  }

  /**
   * add the node that corresponds to the index-th node in graph(GraphData)
   * without changing his color
   * function used only to assign to default group at initialization
   * @param vertex    vertex to process
   */
  void addNode_WO_COLOR(GraphVertex vertex) {
    nodes.add(vertex);
    changed = true;

    Group clt = graphData.getGroup(graphData.getIndexToGroupName()[vertex.getId()]);
    if (clt != null) {
      clt.removeNode(vertex);
    }
    graphData.getIndexToGroupName()[vertex.getId()] = name;
  }

  /**
   * remove from group the given node
   * @param vertex
   */
  public void removeNode(GraphVertex vertex) {
    nodes.remove(vertex);
    changed = true;
  }

  /**
   * return an iterator on the index of the group's nodes
   * @return iterator
   */
  @Override
  public Iterator<GraphVertex> iterator() {
    return nodes.iterator();
  }

  /**
   * adds nodes to a group in function of a given pattern
   * @param pattern
   * @param mode the way of using the pattern.
   */
  public void addViaPattern(String pattern, int mode) {
    for (GraphVertex graphVertex : graphData.getVertices()) {
      if (mode == Group.EQUALS) {
        if (graphVertex.getName().equals(pattern)) {
          addNode(graphVertex);
        }
      } else if (mode == Group.CONTAINS) {
        if (graphVertex.getName().matches(".*" + pattern + ".*")) {
          addNode(graphVertex);
        }
      } else if (mode == Group.STARTS) {
        if (graphVertex.getName().startsWith(pattern)) {
          addNode(graphVertex);
        }
      } else if (mode == Group.ENDS) {
        if (graphVertex.getName().endsWith(pattern)) {
          addNode(graphVertex);
        }
      }
    }
  }

  public void filter(String pattern, int mode, boolean keep) {
    int end = nodes.size();
    boolean match[] = new boolean[end];

    for (int i = 0; i < end; i++) {
      match[i] = !keep;
      GraphVertex graphVertex = nodes.get(i);
      if (mode == Group.EQUALS) {
        if (graphVertex.getName().equals(pattern)) {
          match[i] = keep;
        }
      } else if (mode == Group.CONTAINS) {
        if (graphVertex.getName().matches(".*" + pattern + ".*")) {
          match[i] = keep;
        }
      } else if (mode == Group.STARTS) {
        if (graphVertex.getName().startsWith(pattern)) {
          match[i] = keep;
        }
      } else if (mode == Group.ENDS) {
        if (graphVertex.getName().endsWith(pattern)) {
          match[i] = keep;
        }
      }
    }

    GraphVertex selected[] = new GraphVertex[end];
    for (int i = 0; i < end; ++i) {
      if (!match[i]) {
        selected[i] = nodes.get(i);
      }
    }

    Group defaultClt = graphData.getGroup(0);
    for (int i = 0; i < end; ++i) {
      if (selected[i] != null) {
        defaultClt.addNode(selected[i]);
      }
    }
  }

  public List<GraphVertex> getNodes() {
    return nodes;
  }

  /**
   * @return return the color of the group.
   */
  public Color getColor() {
    return color;
  }

  /**
   * @param color color to define.
   */
  public void setColor(Color color) {
    this.color = color;
    for (GraphVertex vertex : nodes) {
      vertex.setColor(color);
    }
  }

  public Shape getShape() {
    return shape;
  }

  public void setShape(Shape shape) {
    assert (shape != null);

    this.shape = shape;

    for (GraphVertex graphVertex : nodes) {
      graphVertex.setShape(shape);
    }
  }

  public boolean matchPattern(String pName) {
    for (String pattern : patterns) {
      if (pName.matches(pattern)) {
        return true;
      }
      if (pName.equals(pattern)) {
        return true;
      }
    }
    return false;
  }

  public void addPattern(String pattern) {
    patterns.add(pattern);
  }

  /**
   * @return return the name of the group.
   */
  public String getName() {
    return name;
  }

  /**
   * @return return the name of the group.
   */
  @Override
  public String toString() {
    return name;
  }

  /**
   * @return return the averageRadius.
   */
  public float getAverageRadius() {
    if (changed) {
      compute();
    }
    return radiusOfGroup.getAverageRadius();
  }

  /**
   * return the x-coordinate of the barycenter
   * @return x
   */
  public float getX() {
    if (changed) {
      compute();
    }
    return radiusOfGroup.getX();
  }

  /**
   * return the y-coordinate of the barycenter
   * @return y
   */
  public float getY() {
    if (changed) {
      compute();
    }
    return radiusOfGroup.getY();
  }

  /**
   * return the z-coordinate of the barycenter
   * @return z
   */
  public float getZ() {
    if (changed) {
      compute();
    }
    return radiusOfGroup.getZ();
  }

  /**
   * compute the informations provided by the group
   */
  private void compute() {
    radiusOfGroup = new RadiusOfGroup(nodes);

    changed = false;
  }

  /**
   * tells the group to recompute its informations
   */
  public void graphchanged() {
    changed = true;
  }

  /**
   * @param kind the kind to set
   */
  public void setKind(GroupKind kind) {
    this.kind = kind;
  }

  /**
   * @return the kind
   */
  public GroupKind getKind() {
    return kind;
  }

  public boolean isVisible() {
    return visible;
  }

  public void setVisible(boolean visible) {
    this.visible = visible;
  }

  public boolean isInfo() {
    return info;
  }

  public void setInfo(boolean info) {
    this.info = info;
  }
}
